import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import threading
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional
import pandas as pd
import os
from pathlib import Path
from PIL import Image, ImageTk

from .data_fetcher import DataFetcher, DataFetchError
from .data_storage import DataStorage
from .schedule_manager import ScheduleManager, TaskStatus
from .data_source_manager import DataSourceManager
import sys
import os
from pathlib import Path

# 添加项目根目录到Python路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))

from ashare_update import AShareUpdater
from .utils.config import get_settings
from .utils.logger import get_logger
from .utils.validators import DataValidator, ValidationError

logger = get_logger("MainWindow")


class MainWindow:
    """主界面窗口"""

    def __init__(self, root: tk.Tk):
        self.root = root
        self.root.title("量化数据获取系统")
        self.root.geometry("900x700")

        # 初始化组件
        self.data_fetcher = DataFetcher()
        self.data_storage = DataStorage()
        self.schedule_manager = ScheduleManager()
        self.data_source_manager = DataSourceManager(auto_check=False)
        self.ashare_updater = AShareUpdater()
        self.settings = get_settings()

        # 状态变量
        self.is_fetching = False
        self.is_syncing = False
        self.current_symbols = []
        self.fetch_thread = None
        self.sync_thread = None

        # 初始化变量（兼容性）
        self.symbol_var = tk.StringVar()
        self.symbol_tree = None
        self.log_text = None
        self.interval_var = tk.StringVar(value="1d")
        self.fetch_source_var = tk.StringVar(value="智能选择")
        self.fetch_button = None
        self.status_label = None

        # 设置GUI
        self.setup_gui()

        # 启动定时任务管理器
        self.schedule_manager.start()

        # 加载配置中的任务
        self.schedule_manager.load_tasks_from_config()

        # 启动状态更新
        self.update_status()

    def setup_gui(self):
        """设置GUI界面"""
        # 设置窗口最小尺寸
        self.root.minsize(1000, 750)

        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="5")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # 配置网格权重
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(0, weight=1)
        main_frame.rowconfigure(1, weight=1)

        # 标题栏
        self.setup_title_bar(main_frame)

        # 创建notebook（选项卡）
        self.notebook = ttk.Notebook(main_frame)
        self.notebook.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(5, 0))

        # 创建选项卡
        self.setup_kline_tab()
        self.setup_sync_tab()
        self.setup_schedule_tab()
        self.setup_data_source_tab()

        # 绑定事件
        self.kline_symbol_combo.bind('<Return>', lambda e: self.show_kline())
        self.kline_symbol_combo.bind('<<ComboboxSelected>>', lambda e: self.show_kline())

        # 初始化数据源状态显示（不进行网络检查）
        self.update_data_source_status()

        # 启动自动更新（暂时禁用以避免启动时的网络请求）
        # self.update_sync_status()

    def setup_title_bar(self, parent):
        """设置标题栏"""
        title_frame = ttk.Frame(parent)
        title_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
        title_frame.columnconfigure(1, weight=1)

        # 标题
        title_label = ttk.Label(title_frame, text="量化数据获取系统",
                               font=('Microsoft YaHei', 16, 'bold'))
        title_label.grid(row=0, column=0, sticky=tk.W)

        # 状态指示器
        status_frame = ttk.Frame(title_frame)
        status_frame.grid(row=0, column=1, sticky=tk.E)

        # 数据源状态
        self.active_source_label = ttk.Label(status_frame, text="数据源: 检测中...",
                                           font=('Microsoft YaHei', 9))
        self.active_source_label.pack(side=tk.RIGHT, padx=(10, 0))

        # 系统状态
        self.system_status_label = ttk.Label(status_frame, text="系统: 就绪",
                                           font=('Microsoft YaHei', 9))
        self.system_status_label.pack(side=tk.RIGHT)

    def setup_kline_tab(self):
        """设置K线图选项卡"""
        kline_frame = ttk.Frame(self.notebook, padding="8")
        self.notebook.add(kline_frame, text="K线图")

        # 配置网格权重
        kline_frame.columnconfigure(0, weight=1)
        kline_frame.rowconfigure(1, weight=1)

        # === 控制区域 ===
        control_frame = ttk.LabelFrame(kline_frame, text="K线控制", padding="8")
        control_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 8))
        control_frame.columnconfigure(1, weight=1)

        # 股票搜索和选择
        ttk.Label(control_frame, text="股票搜索:").grid(row=0, column=0, sticky=tk.W)

        # 搜索框
        self.kline_search_var = tk.StringVar()
        self.kline_search_entry = ttk.Entry(control_frame, textvariable=self.kline_search_var, width=15)
        self.kline_search_entry.grid(row=0, column=1, sticky=tk.W, padx=(8, 4))
        self.kline_search_entry.bind('<KeyRelease>', self.on_search_changed)

        # 股票代码选择
        self.kline_symbol_var = tk.StringVar()

        # 获取可用的股票代码列表
        self.available_symbols = self.get_available_stock_symbols_with_names()

        # 使用Combobox让用户选择股票代码
        self.kline_symbol_combo = ttk.Combobox(control_frame, textvariable=self.kline_symbol_var,
                                              values=[f"{code} - {name}" for code, name in self.available_symbols],
                                              width=20, state="readonly")
        self.kline_symbol_combo.grid(row=0, column=2, sticky=tk.W, padx=(4, 8))

        # 设置默认值
        if self.available_symbols:
            first_item = f"{self.available_symbols[0][0]} - {self.available_symbols[0][1]}"
            self.kline_symbol_var.set(first_item)

        # 时间周期选择
        ttk.Label(control_frame, text="周期:").grid(row=0, column=3, sticky=tk.W, padx=(8, 0))
        self.kline_period_var = tk.StringVar(value="1d")
        period_combo = ttk.Combobox(control_frame, textvariable=self.kline_period_var,
                                   values=["1d", "1h", "30m", "15m", "5m"],
                                   width=8, state="readonly")
        period_combo.grid(row=0, column=4, sticky=tk.W, padx=(8, 8))

        # 显示天数
        ttk.Label(control_frame, text="天数:").grid(row=0, column=5, sticky=tk.W, padx=(8, 0))
        self.kline_days_var = tk.StringVar(value="60")
        days_combo = ttk.Combobox(control_frame, textvariable=self.kline_days_var,
                                 values=["30", "60", "120", "250", "500"],
                                 width=8, state="readonly")
        days_combo.grid(row=0, column=6, sticky=tk.W, padx=(8, 8))

        # 图表类型选择
        ttk.Label(control_frame, text="图表类型:").grid(row=1, column=0, sticky=tk.W, pady=(8, 0))
        self.use_plotly_var = tk.BooleanVar(value=True)  # 默认使用Plotly
        plotly_radio = ttk.Radiobutton(control_frame, text="现代化Plotly图表",
                                      variable=self.use_plotly_var, value=True)
        plotly_radio.grid(row=1, column=1, sticky=tk.W, padx=(8, 4), pady=(8, 0))

        matplotlib_radio = ttk.Radiobutton(control_frame, text="传统matplotlib图表",
                                          variable=self.use_plotly_var, value=False)
        matplotlib_radio.grid(row=1, column=2, sticky=tk.W, padx=(4, 8), pady=(8, 0))

        # 按钮区域
        button_frame = ttk.Frame(control_frame)
        button_frame.grid(row=1, column=7, sticky=tk.E, padx=(8, 0), pady=(8, 0))

        ttk.Button(button_frame, text="显示K线", command=self.show_kline, width=10).pack(side=tk.LEFT, padx=(0, 4))
        ttk.Button(button_frame, text="保存图片", command=self.save_kline_image, width=10).pack(side=tk.LEFT)

        # 技术指标选择
        indicator_frame = ttk.Frame(control_frame)
        indicator_frame.grid(row=2, column=0, columnspan=8, sticky=(tk.W, tk.E), pady=(8, 0))

        ttk.Label(indicator_frame, text="技术指标:").pack(side=tk.LEFT)

        self.show_ma_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(indicator_frame, text="移动平均线", variable=self.show_ma_var).pack(side=tk.LEFT, padx=(8, 0))

        self.show_volume_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(indicator_frame, text="成交量", variable=self.show_volume_var).pack(side=tk.LEFT, padx=(8, 0))

        self.show_macd_var = tk.BooleanVar(value=False)
        ttk.Checkbutton(indicator_frame, text="MACD", variable=self.show_macd_var).pack(side=tk.LEFT, padx=(8, 0))

        self.show_rsi_var = tk.BooleanVar(value=False)
        ttk.Checkbutton(indicator_frame, text="RSI", variable=self.show_rsi_var).pack(side=tk.LEFT, padx=(8, 0))

        # === 主要内容区域 ===
        main_content = ttk.Frame(kline_frame)
        main_content.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(8, 0))
        main_content.columnconfigure(1, weight=1)
        main_content.rowconfigure(0, weight=1)

        # === 股票信息面板 ===
        info_frame = ttk.LabelFrame(main_content, text="股票信息", padding="8")
        info_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 8))
        info_frame.configure(width=250)

        # 股票基本信息
        self.stock_info_text = tk.Text(info_frame, width=30, height=15, wrap=tk.WORD,
                                      font=("Consolas", 9), state=tk.DISABLED)
        info_scrollbar = ttk.Scrollbar(info_frame, orient=tk.VERTICAL, command=self.stock_info_text.yview)
        self.stock_info_text.configure(yscrollcommand=info_scrollbar.set)

        self.stock_info_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        info_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))

        info_frame.columnconfigure(0, weight=1)
        info_frame.rowconfigure(0, weight=1)

        # === K线图显示区域 ===
        self.kline_chart_frame = ttk.LabelFrame(main_content, text="K线图表", padding="8")
        self.kline_chart_frame.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
        self.kline_chart_frame.columnconfigure(0, weight=1)
        self.kline_chart_frame.rowconfigure(0, weight=1)

        # 创建matplotlib图表
        try:
            import matplotlib.pyplot as plt
            from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
            from matplotlib.figure import Figure

            # 设置中文字体
            plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'DejaVu Sans']
            plt.rcParams['axes.unicode_minus'] = False

            # 创建图形
            self.kline_fig = Figure(figsize=(12, 8), dpi=100)
            self.kline_canvas = FigureCanvasTkAgg(self.kline_fig, self.kline_chart_frame)
            self.kline_canvas.get_tk_widget().grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

            # 添加工具栏
            toolbar_frame = ttk.Frame(self.kline_chart_frame)
            toolbar_frame.grid(row=1, column=0, sticky=(tk.W, tk.E))
            self.kline_toolbar = NavigationToolbar2Tk(self.kline_canvas, toolbar_frame)
            self.kline_toolbar.update()

            # 初始化图表
            self.init_kline_chart()

        except ImportError:
            # 如果没有matplotlib，显示提示信息
            info_label = ttk.Label(self.kline_chart_frame, text="请安装matplotlib库以显示K线图:\npip install matplotlib",
                                  justify=tk.CENTER, font=('Microsoft YaHei', 12))
            info_label.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

    def setup_sync_tab(self):
        """设置数据同步选项卡"""
        sync_frame = ttk.Frame(self.notebook, padding="10")
        self.notebook.add(sync_frame, text="数据同步")

        # 配置网格权重
        sync_frame.columnconfigure(0, weight=1)
        sync_frame.columnconfigure(1, weight=2)
        sync_frame.rowconfigure(1, weight=1)

        # === 同步控制区域 ===
        control_frame = ttk.LabelFrame(sync_frame, text="同步控制", padding="10")
        control_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
        control_frame.columnconfigure(1, weight=1)

        # 同步选项
        ttk.Label(control_frame, text="同步模式:").grid(row=0, column=0, sticky=tk.W)
        self.sync_mode_var = tk.StringVar(value="全量更新")
        sync_mode_combo = ttk.Combobox(control_frame, textvariable=self.sync_mode_var,
                                      values=["全量更新", "增量更新", "快速更新", "失败重试", "仅检查状态"],
                                      width=25, state="readonly")
        sync_mode_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0))

        # 数据源选择
        ttk.Label(control_frame, text="数据源:").grid(row=1, column=0, sticky=tk.W, pady=(5, 0))
        self.sync_source_var = tk.StringVar(value="智能选择")
        self.sync_source_combo = ttk.Combobox(control_frame, textvariable=self.sync_source_var,
                                             values=["智能选择", "BaoStock", "Tushare", "EFinance", "EastMoney", "Sina", "YahooFinance", "AKShare"],
                                             width=25, state="readonly")
        self.sync_source_combo.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(5, 0), pady=(5, 0))

        # 同步按钮
        self.sync_button = ttk.Button(control_frame, text="开始同步", command=self.start_sync)
        self.sync_button.grid(row=0, column=2, padx=(10, 0))

        # 检查状态按钮
        ttk.Button(control_frame, text="检查状态", command=self.check_sync_status).grid(row=0, column=3, padx=(5, 0))

        # 同步所有股票按钮
        self.sync_all_button = ttk.Button(control_frame, text="同步所有股票", command=self.start_sync_all)
        self.sync_all_button.grid(row=1, column=2, columnspan=2, pady=(5, 0))

        # 过滤选项
        filter_frame = ttk.Frame(control_frame)
        filter_frame.grid(row=2, column=0, columnspan=4, pady=(10, 0))

        self.filter_st_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(filter_frame, text="排除ST股", variable=self.filter_st_var).pack(side=tk.LEFT, padx=(0, 5))

        self.filter_growth_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(filter_frame, text="排除创业板", variable=self.filter_growth_var).pack(side=tk.LEFT, padx=(0, 5))

        self.filter_beijing_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(filter_frame, text="排除北证", variable=self.filter_beijing_var).pack(side=tk.LEFT)

        # 同步进度
        self.sync_progress_var = tk.DoubleVar()
        self.sync_progress_bar = ttk.Progressbar(control_frame, variable=self.sync_progress_var,
                                                maximum=100, length=300)
        self.sync_progress_bar.grid(row=3, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(10, 0))

        # 同步状态标签
        self.sync_status_label = ttk.Label(control_frame, text="准备就绪")
        self.sync_status_label.grid(row=4, column=0, columnspan=4, sticky=tk.W)

        # === 数据状态区域 ===
        status_frame = ttk.LabelFrame(sync_frame, text="数据状态", padding="10")
        status_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))

        # 数据统计标签
        stats_frame = ttk.Frame(status_frame)
        stats_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))

        self.total_stocks_label = ttk.Label(stats_frame, text="总股票数: 0")
        self.total_stocks_label.grid(row=0, column=0, sticky=tk.W, padx=(0, 10))

        self.up_to_date_label = ttk.Label(stats_frame, text="已更新: 0")
        self.up_to_date_label.grid(row=0, column=1, sticky=tk.W, padx=(0, 10))

        self.outdated_label = ttk.Label(stats_frame, text="需要更新: 0")
        self.outdated_label.grid(row=0, column=2, sticky=tk.W, padx=(0, 10))

        self.update_rate_label = ttk.Label(stats_frame, text="更新率: 0%")
        self.update_rate_label.grid(row=1, column=0, sticky=tk.W, padx=(0, 10))

        self.last_update_label = ttk.Label(stats_frame, text="最后更新: 未更新")
        self.last_update_label.grid(row=1, column=1, columnspan=2, sticky=tk.W, padx=(0, 10))

        # 数据列表
        ttk.Label(status_frame, text="股票数据列表:").grid(row=1, column=0, sticky=tk.W, pady=(5, 0))

        # 创建treeview显示数据状态
        columns = ('symbol', 'market', 'last_date', 'record_count', 'status')
        self.data_tree = ttk.Treeview(status_frame, columns=columns, height=15, show='headings')
        self.data_tree.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # 设置列
        self.data_tree.heading('symbol', text='股票代码')
        self.data_tree.heading('market', text='市场')
        self.data_tree.heading('last_date', text='最后日期')
        self.data_tree.heading('record_count', text='记录数')
        self.data_tree.heading('status', text='状态')

        # 设置列宽
        self.data_tree.column('symbol', width=80)
        self.data_tree.column('market', width=60)
        self.data_tree.column('last_date', width=100)
        self.data_tree.column('record_count', width=80)
        self.data_tree.column('status', width=80)

        # 滚动条
        data_scrollbar = ttk.Scrollbar(status_frame, orient=tk.VERTICAL, command=self.data_tree.yview)
        data_scrollbar.grid(row=2, column=1, sticky=(tk.N, tk.S))
        self.data_tree.config(yscrollcommand=data_scrollbar.set)

        # === 同步日志区域 ===
        sync_log_frame = ttk.LabelFrame(sync_frame, text="同步日志", padding="10")
        sync_log_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(10, 0))
        sync_log_frame.columnconfigure(0, weight=1)
        sync_log_frame.rowconfigure(0, weight=1)

        # 同步日志文本框
        self.sync_log_text = scrolledtext.ScrolledText(sync_log_frame, height=15, wrap=tk.WORD)
        self.sync_log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # 清除日志按钮
        ttk.Button(sync_log_frame, text="清除日志",
                  command=self.clear_sync_log).grid(row=1, column=0, sticky=tk.E, pady=(5, 0))

        # 初始化时加载本地股票数据
        self.load_local_stock_data()

        # 设置日志处理器，将日志输出到GUI
        self.setup_gui_logger()

    def setup_schedule_tab(self):
        """设置定时任务选项卡"""
        schedule_frame = ttk.Frame(self.notebook, padding="10")
        self.notebook.add(schedule_frame, text="定时任务")

        # 配置网格权重
        schedule_frame.columnconfigure(1, weight=1)
        schedule_frame.rowconfigure(2, weight=1)

        # === 定时任务设置区域 ===
        task_setup_frame = ttk.LabelFrame(schedule_frame, text="定时任务设置", padding="10")
        task_setup_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
        task_setup_frame.columnconfigure(1, weight=1)

        # 定时时间设置
        ttk.Label(task_setup_frame, text="执行时间:").grid(row=0, column=0, sticky=tk.W)
        self.schedule_time_var = tk.StringVar(value="09:30")
        time_entry = ttk.Entry(task_setup_frame, textvariable=self.schedule_time_var, width=10)
        time_entry.grid(row=0, column=1, sticky=tk.W, padx=(5, 0))

        # 定时任务按钮
        ttk.Button(task_setup_frame, text="添加定时任务",
                  command=self.add_scheduled_task).grid(row=0, column=2, padx=(10, 0))

        # === 任务列表区域 ===
        task_list_frame = ttk.LabelFrame(schedule_frame, text="定时任务列表", padding="10")
        task_list_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
        task_list_frame.columnconfigure(0, weight=1)
        task_list_frame.rowconfigure(0, weight=1)

        # 任务列表
        self.task_tree = ttk.Treeview(task_list_frame, columns=('symbol', 'interval', 'status', 'next_run'),
                                     height=10, show='headings')
        self.task_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # 设置列
        self.task_tree.heading('symbol', text='股票代码')
        self.task_tree.heading('interval', text='频率')
        self.task_tree.heading('status', text='状态')
        self.task_tree.heading('next_run', text='下次运行')

        self.task_tree.column('symbol', width=100)
        self.task_tree.column('interval', width=80)
        self.task_tree.column('status', width=80)
        self.task_tree.column('next_run', width=120)

        # 滚动条
        task_scrollbar = ttk.Scrollbar(task_list_frame, orient=tk.VERTICAL, command=self.task_tree.yview)
        task_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        self.task_tree.config(yscrollcommand=task_scrollbar.set)

        # 任务操作按钮
        task_button_frame = ttk.Frame(task_list_frame)
        task_button_frame.grid(row=1, column=0, columnspan=2, pady=(5, 0))

        ttk.Button(task_button_frame, text="删除任务",
                  command=self.remove_scheduled_task).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(task_button_frame, text="刷新状态",
                  command=self.refresh_tasks).pack(side=tk.LEFT)

    def add_symbol(self):
        """添加股票代码（已禁用，使用K线图功能）"""
        messagebox.showinfo("提示", "请使用K线图选项卡查看股票数据")

    def remove_symbol(self):
        """删除股票代码（已禁用）"""
        messagebox.showinfo("提示", "请使用K线图选项卡查看股票数据")

    def clear_symbols(self):
        """清空股票列表（已禁用）"""
        messagebox.showinfo("提示", "请使用K线图选项卡查看股票数据")

    def log_message(self, message: str):
        """添加日志消息（兼容性方法）"""
        print(f"[LOG] {message}")

    def clear_log(self):
        """清除日志（兼容性方法）"""
        pass

    def save_log(self):
        """保存日志（兼容性方法）"""
        pass

    def refresh_log(self):
        """刷新日志（兼容性方法）"""
        pass

    def load_preset_symbols(self):
        """加载预设股票代码（已禁用）"""
        messagebox.showinfo("提示", "请使用K线图选项卡查看股票数据")

    def import_symbols(self):
        """导入股票代码（已禁用）"""
        messagebox.showinfo("提示", "请使用K线图选项卡查看股票数据")

    def start_fetch(self):
        """开始获取数据（已禁用）"""
        messagebox.showinfo("提示", "请使用数据同步功能获取数据")

    def fetch_data(self):
        """获取数据（已禁用）"""
        messagebox.showinfo("提示", "请使用数据同步功能获取数据")

    def _fetch_data_thread(self, *args, **kwargs):
        """数据获取线程（已禁用）"""
        pass

    def _update_symbol_status(self, symbol: str, status: str):
        """更新股票状态（兼容性方法）"""
        pass

    def _fetch_complete(self, successful_count: int, total_count: int):
        """数据获取完成（兼容性方法）"""
        pass

    def _fetch_error(self, error_message: str):
        """数据获取错误（兼容性方法）"""
        pass

    def remove_symbol(self):
        """删除股票代码"""
        selection = self.symbol_tree.selection()
        if not selection:
            messagebox.showinfo("提示", "请先选择要删除的股票代码")
            return

        item = selection[0]
        values = self.symbol_tree.item(item, 'values')
        if values:
            symbol = values[0]
            self.symbol_tree.delete(item)
            if symbol in self.current_symbols:
                self.current_symbols.remove(symbol)
            self.log_message(f"删除股票代码: {symbol}")

    def fetch_data(self):
        """获取数据"""
        if not self.current_symbols:
            messagebox.showwarning("警告", "请先添加股票代码")
            return

        if self.is_fetching:
            messagebox.showinfo("提示", "正在获取数据，请稍候...")
            return

        symbols = self.current_symbols.copy()
        interval = self.interval_var.get()

        # 在新线程中执行数据获取
        self.is_fetching = True
        self.fetch_button.config(state='disabled')
        self.progress_var.set(0)
        self.status_label.config(text="正在获取数据...")

        self.fetch_thread = threading.Thread(
            target=self._fetch_data_thread,
            args=(symbols, interval),
            daemon=True
        )
        self.fetch_thread.start()

    def _fetch_data_thread(self, symbols: List[str], interval: str, source_preference: str = "智能选择"):
        """数据获取线程"""
        try:
            total_symbols = len(symbols)
            successful_count = 0

            for i, symbol in enumerate(symbols):
                try:
                    # 更新状态
                    self.root.after(0, lambda s=symbol, idx=i+1, total=total_symbols:
                                  self.status_label.config(text=f"正在获取 {s} ({idx}/{total})"))
                    self.root.after(0, lambda progress=(i + 1) / total_symbols * 100:
                                  self.progress_var.set(progress))

                    # 根据用户选择的数据源获取数据
                    if source_preference == "智能选择":
                        data = self.data_fetcher.fetch_with_smart_source_selection(symbol, interval)
                    else:
                        # 使用指定数据源
                        data = self.data_fetcher.fetch_full_history(symbol, interval)

                    if not data.empty:
                        # 验证和清理数据
                        cleaned_data = self.data_fetcher.validate_and_clean_data(data, symbol)

                        if not cleaned_data.empty:
                            # 保存数据
                            self.data_storage.save_data(symbol, cleaned_data, interval)
                            successful_count += 1

                            # 更新界面中的股票状态
                            self.root.after(0, lambda s=symbol: self._update_symbol_status(s, "获取成功"))
                            self.root.after(0, lambda s=symbol, count=len(cleaned_data):
                                          self.log_message(f"成功获取 {s} 数据: {count} 条记录"))
                        else:
                            self.root.after(0, lambda s=symbol: self._update_symbol_status(s, "数据无效"))
                            self.root.after(0, lambda s=symbol: self.log_message(f"{s} 数据验证失败"))
                    else:
                        self.root.after(0, lambda s=symbol: self._update_symbol_status(s, "无数据"))
                        self.root.after(0, lambda s=symbol: self.log_message(f"未获取到 {s} 的数据"))

                except DataFetchError as e:
                    self.root.after(0, lambda s=symbol: self._update_symbol_status(s, "获取失败"))
                    self.root.after(0, lambda s=symbol, err=str(e): self.log_message(f"获取 {s} 数据失败: {err}"))
                except Exception as e:
                    self.root.after(0, lambda s=symbol: self._update_symbol_status(s, "错误"))
                    self.root.after(0, lambda s=symbol, err=str(e): self.log_message(f"获取 {s} 数据时发生错误: {err}"))

            # 显示结果
            self.root.after(0, self._fetch_complete, successful_count, total_symbols)

        except Exception as e:
            self.root.after(0, self._fetch_error, str(e))

    def _update_symbol_status(self, symbol: str, status: str):
        """更新股票状态显示"""
        try:
            for item in self.symbol_tree.get_children():
                values = self.symbol_tree.item(item, 'values')
                if values and values[0] == symbol:
                    # 更新状态列
                    new_values = list(values)
                    new_values[2] = status
                    self.symbol_tree.item(item, values=new_values)
                    break
        except Exception as e:
            logger.error(f"更新股票状态失败: {e}")

    def update_data_source_status(self):
        """更新数据源状态显示"""
        try:
            # 获取当前活跃数据源
            active_source = self.data_source_manager.active_source
            if active_source:
                self.active_source_label.config(text=f"数据源: {active_source}")
            else:
                self.active_source_label.config(text="数据源: 智能选择")

            # 定期更新
            self.root.after(5000, self.update_data_source_status)
        except Exception as e:
            logger.error(f"更新数据源状态失败: {e}")

    def delayed_data_source_init(self):
        """延迟初始化数据源（避免启动时阻塞）"""
        try:
            # 在后台线程中检查数据源
            import threading
            def check_sources():
                self.data_source_manager.check_all_sources()
                # 在主线程中更新UI
                self.root.after(0, self.update_data_source_status)

            thread = threading.Thread(target=check_sources, daemon=True)
            thread.start()
        except Exception as e:
            logger.error(f"延迟初始化数据源失败: {e}")

    def get_symbol_info(self, symbol: str) -> Dict[str, Any]:
        """获取股票信息"""
        try:
            # 使用数据源管理器获取股票信息
            sources = self.data_fetcher.data_source_manager.get_best_sources_for_task('stock_info')

            for source_name in sources:
                try:
                    source = self.data_fetcher.data_source_manager.sources.get(source_name)
                    if source:
                        info = source.get_stock_info(symbol)
                        if info:
                            return info
                except Exception as e:
                    logger.warning(f"数据源 {source_name} 获取股票信息失败: {e}")
                    continue

            # 如果所有数据源都失败，返回基本信息
            return {'symbol': symbol, 'name': 'Unknown', 'market': 'Unknown'}

        except Exception as e:
            logger.error(f"获取股票信息失败 {symbol}: {e}")
            return {'symbol': symbol, 'name': 'Unknown', 'market': 'Unknown'}

    def _fetch_complete(self, successful_count: int, total_count: int):
        """数据获取完成"""
        self.is_fetching = False
        self.fetch_button.config(state='normal')
        self.progress_var.set(100)
        self.status_label.config(text=f"获取完成: {successful_count}/{total_count}")

        if successful_count == total_count:
            messagebox.showinfo("完成", f"成功获取 {total_count} 个股票的数据")
        else:
            messagebox.showwarning("完成", f"成功获取 {successful_count}/{total_count} 个股票的数据")

    def _fetch_error(self, error_message: str):
        """数据获取错误"""
        self.is_fetching = False
        self.fetch_button.config(state='normal')
        self.status_label.config(text="获取失败")
        self.log_message(f"数据获取失败: {error_message}")
        messagebox.showerror("错误", f"数据获取失败: {error_message}")

    def add_scheduled_task(self):
        """添加定时任务"""
        if not self.current_symbols:
            messagebox.showwarning("警告", "请先添加股票代码")
            return

        schedule_time = self.schedule_time_var.get().strip()
        if not DataValidator.validate_time_format(schedule_time):
            messagebox.showerror("错误", "时间格式错误，请使用 HH:MM 格式")
            return

        interval = self.interval_var.get()

        try:
            added_count = 0
            for symbol in self.current_symbols:
                task_id = self.schedule_manager.add_task(symbol, interval, schedule_time)
                if task_id:
                    added_count += 1
                    self.log_message(f"添加定时任务: {symbol} ({interval}) at {schedule_time}")

            if added_count > 0:
                messagebox.showinfo("成功", f"成功添加 {added_count} 个定时任务")
                self.refresh_tasks()
            else:
                messagebox.showerror("错误", "添加定时任务失败")

        except Exception as e:
            messagebox.showerror("错误", f"添加定时任务失败: {e}")

    def remove_scheduled_task(self):
        """删除定时任务"""
        selection = self.task_tree.selection()
        if not selection:
            return

        item = self.task_tree.item(selection[0])
        task_info = item['values']

        if not task_info:
            return

        symbol = task_info[0]
        interval = task_info[1]

        # 查找并删除任务
        tasks = self.schedule_manager.get_all_tasks()
        for task in tasks:
            if (task['symbol'] == symbol and
                task['interval'] == interval):
                if self.schedule_manager.remove_task(task['task_id']):
                    self.log_message(f"删除定时任务: {symbol} ({interval})")
                    self.refresh_tasks()
                    break

    def refresh_tasks(self):
        """刷新任务列表"""
        # 清空现有项目
        for item in self.task_tree.get_children():
            self.task_tree.delete(item)

        # 添加任务
        tasks = self.schedule_manager.get_all_tasks()
        for task in tasks:
            status_map = {
                'pending': '待执行',
                'running': '运行中',
                'completed': '已完成',
                'failed': '失败',
                'cancelled': '已取消'
            }

            status_text = status_map.get(task['status'], task['status'])
            next_run = task.get('next_run', '')
            if next_run:
                # 只显示时间部分
                try:
                    dt = datetime.fromisoformat(next_run)
                    next_run = dt.strftime('%H:%M')
                except:
                    next_run = ''

            self.task_tree.insert('', tk.END, values=(
                task['symbol'],
                task['interval'],
                status_text,
                next_run
            ))

    def update_status(self):
        """更新状态信息"""
        try:
            self.refresh_tasks()
        except Exception as e:
            self.log_message(f"更新状态失败: {e}")

        # 每秒更新一次
        self.root.after(1000, self.update_status)

    def log_message(self, message: str):
        """添加日志消息"""
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        log_entry = f"[{timestamp}] {message}\n"

        self.log_text.insert(tk.END, log_entry)
        self.log_text.see(tk.END)

        # 限制日志行数
        lines = self.log_text.get('1.0', tk.END).split('\n')
        if len(lines) > 1000:
            self.log_text.delete('1.0', f'{len(lines)-1000}.0')

    def clear_log(self):
        """清除日志"""
        self.log_text.delete('1.0', tk.END)
        self.log_message("日志已清除")

    def save_log(self):
        """保存日志到文件"""
        try:
            from tkinter import filedialog
            filename = filedialog.asksaveasfilename(
                defaultextension=".txt",
                filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
            )
            if filename:
                with open(filename, 'w', encoding='utf-8') as f:
                    f.write(self.log_text.get('1.0', tk.END))
                self.log_message(f"日志已保存到: {filename}")
        except Exception as e:
            self.log_message(f"保存日志失败: {e}")

    def refresh_log(self):
        """刷新日志显示"""
        self.log_message("日志已刷新")

    def clear_symbols(self):
        """清空股票列表"""
        self.symbol_tree.delete(*self.symbol_tree.get_children())
        self.current_symbols.clear()
        self.log_message("股票列表已清空")

    def load_preset_symbols(self):
        """加载预设股票代码"""
        try:
            preset_symbols = self.settings.get("stock_symbols", {}).get("favorites", [])
            for symbol in preset_symbols:
                if symbol not in self.current_symbols:
                    self.current_symbols.append(symbol)
                    # 获取股票名称
                    info = self.data_fetcher.get_symbol_info(symbol)
                    name = info.get('name', 'Unknown')
                    self.symbol_tree.insert('', tk.END, values=(symbol, name, '已添加'))
            self.log_message(f"已加载 {len(preset_symbols)} 个预设股票代码")
        except Exception as e:
            self.log_message(f"加载预设失败: {e}")

    def import_symbols(self):
        """从文件导入股票代码"""
        try:
            from tkinter import filedialog
            filename = filedialog.askopenfilename(
                filetypes=[("文本文件", "*.txt"), ("CSV文件", "*.csv"), ("所有文件", "*.*")]
            )
            if filename:
                with open(filename, 'r', encoding='utf-8') as f:
                    lines = f.readlines()

                imported_count = 0
                for line in lines:
                    symbol = line.strip()
                    if symbol and symbol not in self.current_symbols:
                        self.current_symbols.append(symbol)
                        info = self.data_fetcher.get_symbol_info(symbol)
                        name = info.get('name', 'Unknown')
                        self.symbol_tree.insert('', tk.END, values=(symbol, name, '已导入'))
                        imported_count += 1

                self.log_message(f"从文件导入了 {imported_count} 个股票代码")
        except Exception as e:
            self.log_message(f"导入文件失败: {e}")

    def start_fetch(self):
        """开始获取数据"""
        if not self.current_symbols:
            messagebox.showwarning("警告", "请先添加股票代码")
            return

        if self.is_fetching:
            messagebox.showwarning("警告", "正在获取数据，请稍候")
            return

        self.is_fetching = True
        self.fetch_button.config(state='disabled')
        self.status_label.config(text="准备获取数据...")

        # 获取选择的参数
        interval = self.interval_var.get()
        source_preference = self.fetch_source_var.get()

        # 启动获取线程
        self.fetch_thread = threading.Thread(
            target=self._fetch_data_thread,
            args=(self.current_symbols.copy(), interval, source_preference)
        )
        self.fetch_thread.daemon = True
        self.fetch_thread.start()

    def clear_sync_log(self):
        """清除同步日志"""
        self.sync_log_text.delete('1.0', tk.END)
        self.sync_log_message("同步日志已清除")

    def start_sync(self):
        """开始同步数据"""
        if self.is_syncing:
            messagebox.showinfo("提示", "正在同步数据，请稍候...")
            return

        sync_mode = self.sync_mode_var.get()
        sync_source = self.sync_source_var.get()

        # 在新线程中执行同步
        self.is_syncing = True
        self.sync_button.config(state='disabled')
        self.sync_progress_var.set(0)
        self.sync_status_label.config(text="正在同步数据...")

        self.sync_thread = threading.Thread(
            target=self._sync_data_thread,
            args=(sync_mode, sync_source),
            daemon=True
        )
        self.sync_thread.start()

    def _sync_data_thread(self, sync_mode: str, sync_source: str = "智能选择"):
        """同步数据线程"""
        try:
            self.sync_log_message(f"开始{sync_mode}，使用数据源: {sync_source}")

            # 设置数据源
            if sync_source != "智能选择":
                # 切换到指定数据源
                if sync_source in self.data_source_manager.sources:
                    self.data_source_manager.set_active_source(sync_source)
                    self.sync_log_message(f"已切换到数据源: {sync_source}")
                else:
                    self.sync_log_message(f"数据源 {sync_source} 不可用，使用智能选择")

            if sync_mode == "全量更新":
                result = self.ashare_updater.run_daily_update(update_mode="full")
            elif sync_mode == "增量更新":
                result = self.ashare_updater.run_daily_update(update_mode="incremental")
            elif sync_mode == "快速更新":
                result = self.ashare_updater.run_quick_update(50)
            elif sync_mode == "失败重试":
                # 获取之前失败的股票并重试
                failed_stocks = self.ashare_updater.load_failed_stocks()
                if failed_stocks:
                    self.sync_log_message(f"重试{len(failed_stocks)}只失败的股票")
                    result = self.ashare_updater.run_daily_update(failed_stocks, update_mode="full")
                else:
                    self.sync_log_message("没有需要重试的股票")
                    result = True
            elif sync_mode == "仅检查状态":
                status = self.ashare_updater.check_update_status()
                self.sync_log_message(f"状态检查完成")
                self.sync_log_message(f"总股票数: {status.get('total_stocks', 0)}")
                self.sync_log_message(f"已更新: {status.get('up_to_date', 0)}")
                self.sync_log_message(f"需要更新: {status.get('outdated', 0)}")
                self.sync_log_message(f"更新率: {status.get('update_rate', 0):.1f}%")
                result = True
            else:
                result = False

            # 显示结果
            self.root.after(0, self._sync_complete, result, sync_mode)

        except Exception as e:
            self.root.after(0, self._sync_error, str(e))

    def _sync_complete(self, success: bool, sync_mode: str):
        """同步完成"""
        self.is_syncing = False
        self.sync_button.config(state='normal')
        self.sync_all_button.config(state='normal')
        self.sync_progress_var.set(100)

        if success:
            self.sync_status_label.config(text=f"{sync_mode}同步完成")
            self.sync_log_message(f"{sync_mode}同步完成")
            messagebox.showinfo("完成", f"{sync_mode}同步完成")
        else:
            self.sync_status_label.config(text=f"{sync_mode}同步失败")
            self.sync_log_message(f"{sync_mode}同步失败")
            messagebox.showwarning("完成", f"{sync_mode}同步失败")

        # 刷新数据状态
        self.refresh_data_status()

    def _sync_error(self, error_message: str):
        """同步错误"""
        self.is_syncing = False
        self.sync_button.config(state='normal')
        self.sync_all_button.config(state='normal')
        self.sync_status_label.config(text="同步失败")
        self.sync_log_message(f"同步失败: {error_message}")
        messagebox.showerror("错误", f"同步失败: {error_message}")

    def check_sync_status(self):
        """检查同步状态"""
        try:
            status = self.ashare_updater.check_update_status()

            if status:
                self.total_stocks_label.config(text=f"总股票数: {status['total_stocks']}")
                self.up_to_date_label.config(text=f"已更新: {status['up_to_date']}")
                self.outdated_label.config(text=f"需要更新: {status['outdated']}")
                self.update_rate_label.config(text=f"更新率: {status['update_rate']:.1f}%")

                self.sync_log_message(f"同步状态检查完成:")
                self.sync_log_message(f"  总股票数: {status['total_stocks']}")
                self.sync_log_message(f"  已更新: {status['up_to_date']}")
                self.sync_log_message(f"  需要更新: {status['outdated']}")
                self.sync_log_message(f"  更新率: {status['update_rate']:.1f}%")

                if status['sample_outdated']:
                    self.sync_log_message(f"  需要更新的股票示例: {status['sample_outdated']}")
            else:
                self.sync_log_message("无法获取同步状态")

        except Exception as e:
            self.sync_log_message(f"检查同步状态失败: {e}")

    def refresh_data_status(self):
        """刷新数据状态"""
        try:
            # 清空现有数据
            for item in self.data_tree.get_children():
                self.data_tree.delete(item)

            # 获取数据目录
            data_dir = Path(self.settings['system']['data_directory']) / 'stocks' / 'daily'
            if not data_dir.exists():
                return

            # 统计信息
            total_count = 0
            up_to_date_count = 0
            outdated_count = 0
            failed_count = 0
            last_update_time = None

            # 遍历数据文件
            for file_path in data_dir.glob("*.csv"):
                try:
                    symbol = file_path.stem.split('_')[0]

                    # 获取文件信息
                    info = self.data_storage.get_data_info(symbol, '1d')
                    if info['file_exists']:
                        total_count += 1

                        # 获取最后更新时间
                        file_path = self.data_storage._get_file_path(symbol, '1d')
                        if file_path.exists():
                            modified_time = datetime.fromtimestamp(file_path.stat().st_mtime)
                            if last_update_time is None or modified_time > last_update_time:
                                last_update_time = modified_time

                        # 检查数据是否过期
                        if info['record_count'] > 0:
                            existing_data = self.data_storage.load_data(symbol, '1d')
                            if not existing_data.empty:
                                # 检查Date列是否在列中或索引中
                                if 'Date' in existing_data.columns:
                                    last_date = pd.to_datetime(existing_data['Date'].max())
                                elif existing_data.index.name == 'Date' or isinstance(existing_data.index, pd.DatetimeIndex):
                                    last_date = existing_data.index.max()
                                else:
                                    status = "数据异常"
                                    failed_count += 1
                                    continue

                                days_diff = (datetime.now() - last_date).days

                                if days_diff > 2:
                                    status = "需要更新"
                                    outdated_count += 1
                                else:
                                    status = "已更新"
                                    up_to_date_count += 1
                            else:
                                status = "数据异常"
                                failed_count += 1
                        else:
                            status = "无数据"
                            failed_count += 1

                        # 获取市场信息
                        market = "A股"
                        if symbol.startswith('6'):
                            market = "沪市"
                        elif symbol.startswith('0'):
                            market = "深市"
                        elif symbol.startswith('3'):
                            market = "创业板"
                        elif symbol.startswith('688'):
                            market = "科创板"

                        # 添加到树形视图
                        self.data_tree.insert('', tk.END, values=(
                            symbol,
                            market,
                            info['last_date'] or '未知',
                            info['record_count'],
                            status
                        ))

                except Exception as e:
                    logger.error(f"Error processing file {file_path}: {e}")

            # 更新统计标签
            self.total_stocks_label.config(text=f"总股票数: {total_count}")
            self.up_to_date_label.config(text=f"已更新: {up_to_date_count}")
            self.outdated_label.config(text=f"需要更新: {outdated_count}")

            if total_count > 0:
                update_rate = up_to_date_count / total_count * 100
                self.update_rate_label.config(text=f"更新率: {update_rate:.1f}%")
            else:
                self.update_rate_label.config(text="更新率: 0%")

            if last_update_time:
                self.last_update_label.config(text=f"最后更新: {last_update_time.strftime('%Y-%m-%d %H:%M')}")
            else:
                self.last_update_label.config(text="最后更新: 未更新")

        except Exception as e:
            logger.error(f"Error refreshing data status: {e}")

    def update_sync_status(self):
        """更新同步状态"""
        try:
            # 每30秒刷新一次数据状态
            self.refresh_data_status()
        except Exception as e:
            logger.error(f"Error updating sync status: {e}")

        # 每30秒更新一次
        self.root.after(30000, self.update_sync_status)

    def filter_stocks(self, stocks_df: pd.DataFrame) -> pd.DataFrame:
        """过滤股票列表"""
        try:
            filtered_df = stocks_df.copy()

            # 排除ST股
            if self.filter_st_var.get():
                # 过滤名称中包含ST或PT的股票
                filtered_df = filtered_df[~filtered_df['name'].str.contains('ST|PT', na=False)]
                # 过滤代码中包含ST的股票
                filtered_df = filtered_df[~filtered_df['symbol'].str.contains('ST', na=False)]
                # 过滤退市股
                filtered_df = filtered_df[~filtered_df['name'].str.contains('退市', na=False)]
                self.sync_log_message("已过滤ST、PT和退市股")

            # 排除创业板 (300开头)
            if self.filter_growth_var.get():
                filtered_df = filtered_df[~filtered_df['symbol'].str.startswith('300')]
                self.sync_log_message("已过滤创业板股票")

            # 排除北证 (8开头, 43开头, 83开头, 87开头)
            if self.filter_beijing_var.get():
                beijing_prefixes = ['8', '43', '83', '87']
                filtered_df = filtered_df[~filtered_df['symbol'].str.startswith(tuple(beijing_prefixes))]
                self.sync_log_message("已过滤北证股票")

            return filtered_df

        except Exception as e:
            self.sync_log_message(f"过滤股票失败: {e}")
            return stocks_df

    def get_all_tradeable_stocks(self) -> List[str]:
        """获取所有可交易的股票列表"""
        try:
            self.sync_log_message("正在获取可交易股票列表...")

            # 导入A股列表获取器
            from ..ashare_list import AShareListFetcher

            list_fetcher = AShareListFetcher()
            stocks_df = list_fetcher.get_all_stocks()

            if stocks_df.empty:
                self.sync_log_message("未获取到股票列表")
                return []

            # 过滤股票
            filtered_df = self.filter_stocks(stocks_df)

            # 只保留上市的股票（baostock中状态为'0'表示上市）
            filtered_df = filtered_df[filtered_df['status'] == '0']

            # 排除新三板和其他特殊市场
            exclude_markets = ['新三板', '其他']
            filtered_df = filtered_df[~filtered_df['market_type'].isin(exclude_markets)]

            symbols = filtered_df['symbol'].tolist()
            self.sync_log_message(f"获取到 {len(symbols)} 只可交易股票")

            return symbols

        except Exception as e:
            self.sync_log_message(f"获取可交易股票列表失败: {e}")
            return []

    def start_sync_all(self):
        """开始同步所有股票"""
        if self.is_syncing:
            messagebox.showinfo("提示", "正在同步数据，请稍候...")
            return

        # 获取过滤选项
        filter_options = {
            'exclude_st': self.filter_st_var.get(),
            'exclude_growth': self.filter_growth_var.get(),
            'exclude_beijing': self.filter_beijing_var.get()
        }

        # 确认对话框
        filter_text = []
        if filter_options['exclude_st']:
            filter_text.append("ST股")
        if filter_options['exclude_growth']:
            filter_text.append("创业板")
        if filter_options['exclude_beijing']:
            filter_text.append("北证")

        filter_desc = "、".join(filter_text) if filter_text else "无"

        result = messagebox.askyesno("确认同步",
                                   f"即将同步所有可交易股票\n"
                                   f"过滤选项: {filter_desc}\n"
                                   f"这可能需要较长时间，是否继续？")

        if not result:
            return

        # 获取股票列表
        symbols = self.get_all_tradeable_stocks()
        if not symbols:
            messagebox.showerror("错误", "未获取到可交易股票列表")
            return

        # 在新线程中执行同步
        self.is_syncing = True
        self.sync_all_button.config(state='disabled')
        self.sync_button.config(state='disabled')
        self.sync_progress_var.set(0)
        self.sync_status_label.config(text=f"正在同步 {len(symbols)} 只股票...")

        self.sync_thread = threading.Thread(
            target=self._sync_all_stocks_thread,
            args=(symbols, filter_options),
            daemon=True
        )
        self.sync_thread.start()

    def _sync_all_stocks_thread(self, symbols: List[str], filter_options: Dict[str, bool]):
        """同步所有股票线程"""
        try:
            self.sync_log_message(f"开始同步 {len(symbols)} 只股票")
            self.sync_log_message(f"过滤选项: {filter_options}")

            # 使用ashare_updater进行批量同步
            result = self.ashare_updater.run_daily_update(symbols)

            # 显示结果
            self.root.after(0, self._sync_complete, result, "所有股票")

        except Exception as e:
            self.root.after(0, self._sync_error, str(e))

    def sync_log_message(self, message: str):
        """添加同步日志消息"""
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        log_entry = f"[{timestamp}] {message}\n"

        self.sync_log_text.insert(tk.END, log_entry)
        self.sync_log_text.see(tk.END)

        # 限制日志行数
        lines = self.sync_log_text.get('1.0', tk.END).split('\n')
        if len(lines) > 1000:
            self.sync_log_text.delete('1.0', f'{len(lines)-1000}.0')

    def on_closing(self):
        """窗口关闭事件"""
        try:
            self.schedule_manager.stop()
        except Exception as e:
            logger.error(f"Error stopping schedule manager: {e}")

        self.root.destroy()

    def setup_data_source_tab(self):
        """设置数据源管理选项卡"""
        ds_frame = ttk.Frame(self.notebook, padding="10")
        self.notebook.add(ds_frame, text="数据源管理")

        # 配置网格权重
        ds_frame.columnconfigure(0, weight=1)
        ds_frame.columnconfigure(1, weight=1)
        ds_frame.rowconfigure(1, weight=1)

        # === 数据源状态区域 ===
        status_frame = ttk.LabelFrame(ds_frame, text="数据源状态", padding="10")
        status_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))

        # 创建数据源状态树形视图
        self.ds_tree = ttk.Treeview(status_frame, columns=('status', 'last_check', 'error'), height=6, show='tree headings')
        self.ds_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # 设置列
        self.ds_tree.heading('#0', text='数据源')
        self.ds_tree.heading('status', text='状态')
        self.ds_tree.heading('last_check', text='最后检查')
        self.ds_tree.heading('error', text='错误信息')

        # 设置列宽
        self.ds_tree.column('#0', width=120)
        self.ds_tree.column('status', width=80)
        self.ds_tree.column('last_check', width=120)
        self.ds_tree.column('error', width=200)

        # 滚动条
        ds_scrollbar = ttk.Scrollbar(status_frame, orient=tk.VERTICAL, command=self.ds_tree.yview)
        ds_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        self.ds_tree.config(yscrollcommand=ds_scrollbar.set)

        # === 左侧区域 ===
        left_frame = ttk.Frame(ds_frame)
        left_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 5))
        left_frame.rowconfigure(1, weight=1)

        # 操作区域
        control_frame = ttk.LabelFrame(left_frame, text="数据源操作", padding="10")
        control_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))

        # 刷新按钮
        ttk.Button(control_frame, text="刷新状态", command=self.refresh_data_sources).pack(side=tk.LEFT, padx=(0, 10))

        # 切换数据源
        switch_frame = ttk.Frame(control_frame)
        switch_frame.pack(side=tk.LEFT, padx=(0, 10))

        ttk.Label(switch_frame, text="切换到:").pack(side=tk.LEFT)
        self.switch_source_var = tk.StringVar()
        self.switch_combo = ttk.Combobox(switch_frame, textvariable=self.switch_source_var, width=15, state="readonly")
        self.switch_combo.pack(side=tk.LEFT, padx=(5, 5))
        ttk.Button(switch_frame, text="切换", command=self.switch_data_source).pack(side=tk.LEFT)

        # 统计信息区域
        stats_frame = ttk.LabelFrame(left_frame, text="数据源统计", padding="10")
        stats_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        stats_frame.rowconfigure(0, weight=1)

        self.stats_text = tk.Text(stats_frame, height=8, wrap=tk.WORD)
        self.stats_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        stats_scrollbar = ttk.Scrollbar(stats_frame, orient=tk.VERTICAL, command=self.stats_text.yview)
        stats_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        self.stats_text.config(yscrollcommand=stats_scrollbar.set)

        # === 右侧日志区域 ===
        log_frame = ttk.LabelFrame(ds_frame, text="操作日志", padding="10")
        log_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(5, 0))
        log_frame.rowconfigure(0, weight=1)

        self.ds_log_text = scrolledtext.ScrolledText(log_frame, height=15, wrap=tk.WORD)
        self.ds_log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        ttk.Button(log_frame, text="清除日志", command=self.clear_ds_log).grid(row=1, column=0, sticky=tk.E, pady=(5, 0))

        # 初始化数据源显示（不进行网络检查）
        self.update_data_source_display()

    def refresh_data_sources(self):
        """刷新数据源状态"""
        self.ds_log_message("开始刷新数据源状态...")

        # 在后台线程中检查数据源可用性
        import threading
        def check_sources():
            try:
                for name, source in self.data_source_manager.sources.items():
                    self.root.after(0, lambda n=name: self.ds_log_message(f"检查数据源: {n}"))
                    source.check_availability()
                    status = source.status
                    error = source.last_error
                    self.root.after(0, lambda n=name, s=status: self.ds_log_message(f"{n}: {s.value}"))
                    if error:
                        self.root.after(0, lambda e=error: self.ds_log_message(f"  错误: {e}"))

                # 在主线程中更新显示
                self.root.after(0, self.update_data_source_display)
                self.root.after(0, lambda: self.ds_log_message("数据源状态刷新完成"))
            except Exception as e:
                self.root.after(0, lambda: self.ds_log_message(f"刷新失败: {e}"))

        thread = threading.Thread(target=check_sources, daemon=True)
        thread.start()

    def switch_data_source(self):
        """切换数据源"""
        source_name = self.switch_source_var.get()
        if not source_name:
            messagebox.showwarning("警告", "请选择要切换的数据源")
            return

        success = self.data_source_manager.set_active_source(source_name)
        if success:
            self.ds_log_message(f"成功切换到数据源: {source_name}")
            self.update_data_source_display()
            messagebox.showinfo("成功", f"已切换到数据源: {source_name}")
        else:
            self.ds_log_message(f"切换数据源失败: {source_name}")
            messagebox.showerror("错误", f"无法切换到数据源: {source_name}")

    def update_data_source_status(self):
        """更新数据源状态显示"""
        try:
            self.update_data_source_display()
            self.update_data_source_stats()
        except Exception as e:
            logger.error(f"更新数据源状态失败: {e}")

        # 每30秒更新一次
        self.root.after(30000, self.update_data_source_status)

    def update_data_source_display(self):
        """更新数据源显示"""
        # 清空现有项
        for item in self.ds_tree.get_children():
            self.ds_tree.delete(item)

        # 获取数据源状态
        sources = self.data_source_manager.get_available_sources()

        # 更新切换下拉框
        available_sources = [s['name'] for s in sources if s['status'] == 'available']
        self.switch_combo['values'] = available_sources
        if available_sources and not self.switch_source_var.get():
            self.switch_source_var.set(available_sources[0])

        # 添加数据源到树形视图
        for source in sources:
            status_text = source['status']
            if source['is_active']:
                status_text += " (当前)"

            last_check = source['last_check'].strftime('%Y-%m-%d %H:%M:%S') if source['last_check'] else "从未"
            error_msg = source['error_message'] or ""

            self.ds_tree.insert('', 'end', text=source['name'],
                              values=(status_text, last_check, error_msg))

    def update_data_source_stats(self):
        """更新数据源统计信息"""
        try:
            stats = []
            sources = self.data_source_manager.get_available_sources()

            # 基本统计
            available_count = len([s for s in sources if s['status'] == 'available'])
            active_source = self.data_source_manager.active_source

            stats.append(f"=== 数据源统计 ===")
            stats.append(f"总数据源数量: {len(sources)}")
            stats.append(f"可用数据源: {available_count}")
            stats.append(f"当前活跃: {active_source}")
            stats.append("")

            # 各数据源详情
            stats.append("=== 数据源详情 ===")
            for source in sources:
                stats.append(f"{source['name']}:")
                stats.append(f"  状态: {source['status']}")
                if source['last_check']:
                    stats.append(f"  最后检查: {source['last_check'].strftime('%Y-%m-%d %H:%M:%S')}")
                if source['error_message']:
                    stats.append(f"  错误: {source['error_message']}")
                stats.append("")

            # 股票统计（暂时禁用以避免网络请求）
            stats.append("=== 股票覆盖统计 ===")
            stats.append("点击'刷新状态'按钮获取股票统计信息")

            # 更新显示
            self.stats_text.delete(1.0, tk.END)
            self.stats_text.insert(1.0, '\n'.join(stats))

        except Exception as e:
            logger.error(f"更新数据源统计失败: {e}")

    def ds_log_message(self, message: str):
        """添加数据源日志消息"""
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        self.ds_log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.ds_log_text.see(tk.END)

    def clear_ds_log(self):
        """清除数据源日志"""
        self.ds_log_text.delete(1.0, tk.END)

    def load_local_stock_data(self):
        """加载本地股票数据到列表中"""
        try:
            import os
            import glob
            from datetime import datetime

            # 获取数据目录路径
            data_dir = os.path.join(os.getcwd(), "data", "stocks", "daily")
            if not os.path.exists(data_dir):
                self.sync_log_message("本地数据目录不存在")
                return

            # 清空现有数据
            for item in self.data_tree.get_children():
                self.data_tree.delete(item)

            # 扫描CSV文件
            csv_files = glob.glob(os.path.join(data_dir, "*_1d.csv"))
            stock_count = 0

            for csv_file in csv_files:
                try:
                    # 从文件名提取股票代码
                    filename = os.path.basename(csv_file)
                    symbol = filename.replace("_1d.csv", "")

                    # 验证是否为A股代码
                    if not self.is_valid_a_stock_code(symbol):
                        continue

                    # 获取文件信息
                    file_stat = os.stat(csv_file)
                    last_modified = datetime.fromtimestamp(file_stat.st_mtime)

                    # 读取CSV文件获取记录数和最后日期
                    try:
                        import pandas as pd
                        df = pd.read_csv(csv_file)
                        record_count = len(df)
                        if not df.empty and 'date' in df.columns:
                            last_date = df['date'].iloc[-1]
                        else:
                            last_date = "无数据"
                    except Exception:
                        record_count = 0
                        last_date = "读取失败"

                    # 判断市场类型
                    if symbol.startswith('00') or symbol.startswith('30'):
                        market = "深圳"
                    elif symbol.startswith('60') or symbol.startswith('68'):
                        market = "上海"
                    elif symbol.startswith('8') or symbol.startswith('4'):
                        market = "北证"
                    else:
                        market = "其他"

                    # 判断状态
                    if record_count > 0:
                        # 检查数据是否为最新（最近3天内）
                        try:
                            last_date_obj = pd.to_datetime(last_date)
                            days_old = (datetime.now() - last_date_obj).days
                            if days_old <= 3:
                                status = "最新"
                            elif days_old <= 7:
                                status = "较新"
                            else:
                                status = "过期"
                        except:
                            status = "未知"
                    else:
                        status = "无数据"

                    # 添加到树形视图
                    self.data_tree.insert('', 'end', values=(symbol, market, last_date, record_count, status))
                    stock_count += 1

                except Exception as e:
                    self.sync_log_message(f"处理文件 {csv_file} 失败: {e}")
                    continue

            # 更新统计信息
            self.total_stocks_label.config(text=f"总股票数: {stock_count}")
            up_to_date = len([item for item in self.data_tree.get_children()
                             if self.data_tree.item(item, 'values')[4] == "最新"])
            self.up_to_date_label.config(text=f"已更新: {up_to_date}")
            self.outdated_label.config(text=f"需要更新: {stock_count - up_to_date}")

            if stock_count > 0:
                update_rate = (up_to_date / stock_count) * 100
                self.update_rate_label.config(text=f"更新率: {update_rate:.1f}%")

            self.sync_log_message(f"加载本地股票数据完成，共 {stock_count} 只股票")

        except Exception as e:
            self.sync_log_message(f"加载本地股票数据失败: {e}")

    def is_valid_a_stock_code(self, code: str) -> bool:
        """验证是否为有效的A股代码"""
        if not code or len(code) != 6:
            return False

        if not code.isdigit():
            return False

        # A股代码规则
        # 上海：60xxxx, 68xxxx (科创板)
        # 深圳：00xxxx (主板), 30xxxx (创业板)
        # 北证：8xxxxx, 4xxxxx
        valid_prefixes = ['00', '30', '60', '68', '8', '4']

        for prefix in valid_prefixes:
            if code.startswith(prefix):
                return True

        return False

    def setup_gui_logger(self):
        """设置GUI日志处理器"""
        import logging

        class GUILogHandler(logging.Handler):
            def __init__(self, text_widget, log_method):
                super().__init__()
                self.text_widget = text_widget
                self.log_method = log_method

            def emit(self, record):
                try:
                    msg = self.format(record)
                    # 在主线程中更新GUI
                    if hasattr(self.text_widget, 'after'):
                        self.text_widget.after(0, lambda: self.log_method(msg))
                except Exception:
                    pass

        # 为AShareUpdater相关的日志添加GUI处理器
        gui_handler = GUILogHandler(self.root, self.sync_log_message)
        gui_handler.setLevel(logging.INFO)
        formatter = logging.Formatter('%(message)s')
        gui_handler.setFormatter(formatter)

        # 添加到相关的logger
        loggers_to_handle = [
            'AShareUpdater',
            'AShareFetcher',
            'AShareListFetcher',
            'DataFetcher',
            'DataStorage'
        ]

        for logger_name in loggers_to_handle:
            logger = logging.getLogger(logger_name)
            logger.addHandler(gui_handler)

    def init_kline_chart(self):
        """初始化K线图表"""
        try:
            # 清空图表
            self.kline_fig.clear()

            # 创建子图
            ax1 = self.kline_fig.add_subplot(2, 1, 1)  # K线图
            ax2 = self.kline_fig.add_subplot(2, 1, 2)  # 成交量

            # 设置标题
            ax1.set_title('请输入股票代码并点击"显示K线"', fontsize=14)
            ax1.text(0.5, 0.5, '暂无数据', ha='center', va='center', transform=ax1.transAxes, fontsize=16)
            ax2.text(0.5, 0.5, '暂无数据', ha='center', va='center', transform=ax2.transAxes, fontsize=16)

            # 调整布局
            self.kline_fig.tight_layout()
            self.kline_canvas.draw()

        except Exception as e:
            print(f"初始化K线图失败: {e}")

    def show_kline(self):
        """显示K线图"""
        symbol_text = self.kline_symbol_var.get().strip()
        if not symbol_text:
            messagebox.showwarning("警告", "请选择股票代码")
            return

        # 从"代码 - 名称"格式中提取代码
        if " - " in symbol_text:
            symbol = symbol_text.split(" - ")[0].strip()
        else:
            symbol = symbol_text

        if not self.is_valid_a_stock_code(symbol):
            messagebox.showerror("错误", f"无效的A股代码: {symbol}")
            return

        period = self.kline_period_var.get()
        days = int(self.kline_days_var.get())

        try:
            # 读取本地数据
            data = self.load_stock_data(symbol, period, days)
            if data is None or data.empty:
                messagebox.showinfo("提示", f"未找到股票 {symbol} 的数据，请先同步数据")
                return

            # 更新股票信息面板
            self.update_stock_info_panel(symbol, data)

            # 绘制K线图 - 提供选择
            if hasattr(self, 'use_plotly_var') and self.use_plotly_var.get():
                self.plot_plotly_kline_chart(data, symbol, period)
            else:
                self.plot_kline_chart(data, symbol, period)

        except Exception as e:
            messagebox.showerror("错误", f"显示K线图失败: {e}")

    def load_stock_data(self, symbol: str, period: str = "1d", days: int = 60):
        """加载股票数据"""
        try:
            import pandas as pd
            import os
            from datetime import datetime, timedelta

            # 构建文件路径
            if period == "1d":
                data_dir = os.path.join(os.getcwd(), "data", "stocks", "daily")
                filename = f"{symbol}_1d.csv"
            elif period == "1h":
                data_dir = os.path.join(os.getcwd(), "data", "stocks", "hourly")
                filename = f"{symbol}_1h.csv"
            else:
                data_dir = os.path.join(os.getcwd(), "data", "stocks", "minute")
                filename = f"{symbol}_{period}.csv"

            file_path = os.path.join(data_dir, filename)

            if not os.path.exists(file_path):
                return None

            # 读取数据
            df = pd.read_csv(file_path)
            if df.empty:
                return None

            # 标准化列名
            column_mapping = {
                'Date': 'date',
                'Open': 'open',
                'High': 'high',
                'Low': 'low',
                'Close': 'close',
                'Volume': 'volume'
            }

            # 重命名列
            for old_col, new_col in column_mapping.items():
                if old_col in df.columns:
                    df[new_col] = df[old_col]

            # 确保日期列存在并转换为datetime
            if 'date' in df.columns:
                df['date'] = pd.to_datetime(df['date'])
                df = df.sort_values('date')

                # 只取最近N天的数据
                if days > 0:
                    cutoff_date = df['date'].max() - timedelta(days=days)
                    df = df[df['date'] >= cutoff_date]
            elif 'Date' in df.columns:
                df['date'] = pd.to_datetime(df['Date'])
                df = df.sort_values('date')

                # 只取最近N天的数据
                if days > 0:
                    cutoff_date = df['date'].max() - timedelta(days=days)
                    df = df[df['date'] >= cutoff_date]

            # 确保必需的列存在
            required_cols = ['open', 'high', 'low', 'close']
            missing_cols = [col for col in required_cols if col not in df.columns]
            if missing_cols:
                print(f"数据缺少必需的列: {missing_cols}")
                return None

            return df

        except Exception as e:
            print(f"加载股票数据失败: {e}")
            return None

    def plot_kline_chart(self, data, symbol: str, period: str):
        """绘制K线图"""
        try:
            import mplfinance as mpf
            import pandas as pd

            # 清空图表
            self.kline_fig.clear()

            # 准备数据
            df = data.copy()

            # 确保数据格式正确
            if 'date' in df.columns:
                df['Date'] = pd.to_datetime(df['date'])
                df.set_index('Date', inplace=True)

            # 重命名列以符合mplfinance要求
            column_mapping = {
                'open': 'Open',
                'high': 'High',
                'low': 'Low',
                'close': 'Close',
                'volume': 'Volume'
            }

            for old_col, new_col in column_mapping.items():
                if old_col in df.columns:
                    df[new_col] = df[old_col]

            # 确保必需的列存在
            required_cols = ['Open', 'High', 'Low', 'Close']
            if not all(col in df.columns for col in required_cols):
                raise ValueError("数据缺少必需的OHLC列")

            # 设置专业的样式
            mc = mpf.make_marketcolors(
                up='#ff4444',      # 红色上涨
                down='#00aa00',    # 绿色下跌
                edge='inherit',
                wick={'up':'#ff4444', 'down':'#00aa00'},
                volume={'up':'#ff444480', 'down':'#00aa0080'},  # 半透明成交量
                ohlc='i'
            )

            s = mpf.make_mpf_style(
                marketcolors=mc,
                gridstyle='-',
                gridcolor='#e0e0e0',
                gridwidth=0.5,
                facecolor='white',
                figcolor='white',
                rc={'font.size': 10, 'axes.labelsize': 10}
            )

            # 准备附加绘图
            addplot = []

            # 添加移动平均线
            if self.show_ma_var.get():
                ma5 = df['Close'].rolling(window=5).mean()
                ma10 = df['Close'].rolling(window=10).mean()
                ma20 = df['Close'].rolling(window=20).mean()

                addplot.extend([
                    mpf.make_addplot(ma5, color='blue', width=1, label='MA5'),
                    mpf.make_addplot(ma10, color='orange', width=1, label='MA10'),
                    mpf.make_addplot(ma20, color='purple', width=1, label='MA20')
                ])

            # 添加MACD
            if self.show_macd_var.get():
                macd_data = self.calculate_macd(df['Close'])
                if macd_data is not None:
                    addplot.append(mpf.make_addplot(macd_data['MACD'], panel=2, color='blue', secondary_y=False))
                    addplot.append(mpf.make_addplot(macd_data['Signal'], panel=2, color='red', secondary_y=False))

            # 添加RSI
            if self.show_rsi_var.get():
                rsi_data = self.calculate_rsi(df['Close'])
                if rsi_data is not None:
                    panel_num = 3 if self.show_macd_var.get() else 2
                    addplot.append(mpf.make_addplot(rsi_data, panel=panel_num, color='purple', secondary_y=False))

            # 获取股票名称
            stock_name = self.get_stock_name(symbol)

            # 计算面板数量
            panel_count = 1  # 主K线图
            if self.show_volume_var.get():
                panel_count += 1
            if self.show_macd_var.get():
                panel_count += 1
            if self.show_rsi_var.get():
                panel_count += 1

            # 动态调整图表大小
            fig_height = max(8, panel_count * 3)

            # 绘制图表
            mpf.plot(df,
                    type='candle',
                    style=s,
                    volume=self.show_volume_var.get(),
                    addplot=addplot if addplot else None,
                    title=f'{stock_name} ({symbol}) - {period.upper()}线图',
                    ylabel='价格 (¥)',
                    ylabel_lower='成交量' if self.show_volume_var.get() else None,
                    figsize=(14, fig_height),
                    fig=self.kline_fig,
                    tight_layout=True,
                    scale_padding={'left': 0.1, 'top': 0.1, 'right': 0.1, 'bottom': 0.1},
                    panel_ratios=(3, 1, 1, 1)[:panel_count] if panel_count > 1 else None)

            self.kline_canvas.draw()

        except Exception as e:
            # 如果mplfinance失败，回退到基础matplotlib绘制
            self.plot_basic_kline_chart(data, symbol, period)

    def plot_basic_kline_chart(self, data, symbol: str, period: str):
        """绘制基础K线图（回退方案）"""
        try:
            import matplotlib.pyplot as plt
            from matplotlib.patches import Rectangle

            # 清空图表
            self.kline_fig.clear()

            # 创建子图
            if self.show_volume_var.get():
                ax1 = self.kline_fig.add_subplot(3, 1, (1, 2))  # K线图占2/3
                ax2 = self.kline_fig.add_subplot(3, 1, 3)       # 成交量占1/3
            else:
                ax1 = self.kline_fig.add_subplot(1, 1, 1)       # 只显示K线图
                ax2 = None

            # 准备数据
            opens = data['open'].values
            highs = data['high'].values
            lows = data['low'].values
            closes = data['close'].values
            volumes = data['volume'].values if 'volume' in data.columns else None

            # 绘制K线
            for i in range(len(data)):
                open_price = opens[i]
                high_price = highs[i]
                low_price = lows[i]
                close_price = closes[i]

                # 确定颜色（红涨绿跌）
                color = 'red' if close_price >= open_price else 'green'

                # 绘制影线
                ax1.plot([i, i], [low_price, high_price], color='black', linewidth=0.5)

                # 绘制实体
                body_height = abs(close_price - open_price)
                body_bottom = min(open_price, close_price)

                if close_price >= open_price:
                    # 阳线（空心）
                    rect = Rectangle((i-0.3, body_bottom), 0.6, body_height,
                                   facecolor='white', edgecolor=color, linewidth=1)
                else:
                    # 阴线（实心）
                    rect = Rectangle((i-0.3, body_bottom), 0.6, body_height,
                                   facecolor=color, edgecolor=color, linewidth=1)

                ax1.add_patch(rect)

            # 设置K线图标题和标签
            ax1.set_title(f'{symbol} K线图 ({period})', fontsize=14, fontweight='bold')
            ax1.set_ylabel('价格 (元)', fontsize=12)
            ax1.grid(True, alpha=0.3)

            # 绘制移动平均线
            if self.show_ma_var.get():
                self.plot_moving_averages(ax1, closes)

            # 绘制成交量
            if ax2 is not None and volumes is not None:
                colors = ['red' if closes[i] >= opens[i] else 'green' for i in range(len(data))]
                ax2.bar(range(len(data)), volumes, color=colors, alpha=0.7, width=0.6)
                ax2.set_ylabel('成交量', fontsize=12)
                ax2.grid(True, alpha=0.3)

            # 设置x轴标签
            if len(data) > 0:
                # 选择合适的日期标签间隔
                step = max(1, len(data) // 10)
                tick_positions = range(0, len(data), step)
                tick_labels = [data.iloc[i]['date'].strftime('%m-%d') for i in tick_positions]

                ax1.set_xticks(tick_positions)
                ax1.set_xticklabels(tick_labels, rotation=45)

                if ax2 is not None:
                    ax2.set_xticks(tick_positions)
                    ax2.set_xticklabels(tick_labels, rotation=45)
                    ax2.set_xlabel('日期', fontsize=12)
                else:
                    ax1.set_xlabel('日期', fontsize=12)

            # 调整布局
            self.kline_fig.tight_layout()
            self.kline_canvas.draw()

        except Exception as e:
            messagebox.showerror("错误", f"绘制K线图失败: {e}")

    def calculate_macd(self, prices, fast=12, slow=26, signal=9):
        """计算MACD指标"""
        try:
            import pandas as pd

            exp1 = prices.ewm(span=fast).mean()
            exp2 = prices.ewm(span=slow).mean()
            macd = exp1 - exp2
            signal_line = macd.ewm(span=signal).mean()
            histogram = macd - signal_line

            return {
                'MACD': macd,
                'Signal': signal_line,
                'Histogram': histogram
            }
        except Exception:
            return None

    def calculate_rsi(self, prices, period=14):
        """计算RSI指标"""
        try:
            import pandas as pd

            delta = prices.diff()
            gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
            loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
            rs = gain / loss
            rsi = 100 - (100 / (1 + rs))

            return rsi
        except Exception:
            return None

    def plot_moving_averages(self, ax, closes):
        """绘制移动平均线"""
        try:
            import pandas as pd

            # 计算移动平均线
            ma5 = pd.Series(closes).rolling(window=5).mean()
            ma10 = pd.Series(closes).rolling(window=10).mean()
            ma20 = pd.Series(closes).rolling(window=20).mean()

            # 绘制移动平均线
            ax.plot(range(len(closes)), ma5, label='MA5', color='blue', linewidth=1)
            ax.plot(range(len(closes)), ma10, label='MA10', color='orange', linewidth=1)
            ax.plot(range(len(closes)), ma20, label='MA20', color='purple', linewidth=1)

            # 添加图例
            ax.legend(loc='upper left', fontsize=10)

        except Exception as e:
            print(f"绘制移动平均线失败: {e}")

    def save_kline_image(self):
        """保存K线图为图片"""
        try:
            from tkinter import filedialog

            symbol = self.kline_symbol_var.get().strip()
            if not symbol:
                messagebox.showwarning("警告", "请先显示K线图")
                return

            # 选择保存路径
            filename = filedialog.asksaveasfilename(
                defaultextension=".png",
                filetypes=[("PNG files", "*.png"), ("JPG files", "*.jpg"), ("All files", "*.*")],
                initialname=f"{symbol}_kline.png"
            )

            if filename:
                self.kline_fig.savefig(filename, dpi=300, bbox_inches='tight')
                messagebox.showinfo("成功", f"K线图已保存到: {filename}")

        except Exception as e:
            messagebox.showerror("错误", f"保存图片失败: {e}")

    def get_available_stock_symbols(self):
        """获取可用的股票代码列表"""
        try:
            import os
            import glob

            # 扫描daily目录下的CSV文件
            daily_dir = os.path.join(os.getcwd(), "data", "stocks", "daily")
            if not os.path.exists(daily_dir):
                return []

            # 获取所有CSV文件
            csv_files = glob.glob(os.path.join(daily_dir, "*_1d.csv"))

            # 提取股票代码
            symbols = []
            for file_path in csv_files:
                filename = os.path.basename(file_path)
                symbol = filename.replace("_1d.csv", "")

                # 保留所有6位数字代码（包括股票、债券等）
                if len(symbol) == 6 and symbol.isdigit():
                    symbols.append(symbol)

            # 排序并返回
            symbols.sort()
            return symbols  # 显示所有可用股票

        except Exception as e:
            print(f"获取股票代码列表失败: {e}")
            return []

    def refresh_symbol_list(self):
        """刷新股票代码列表"""
        try:
            available_symbols = self.get_available_stock_symbols()
            self.kline_symbol_combo['values'] = available_symbols

            # 如果当前选择的代码不在列表中，设置为第一个
            current_symbol = self.kline_symbol_var.get()
            if current_symbol not in available_symbols and available_symbols:
                self.kline_symbol_var.set(available_symbols[0])

        except Exception as e:
            print(f"刷新股票代码列表失败: {e}")

    def get_available_stock_symbols_with_names(self):
        """获取可用的股票代码和名称列表"""
        try:
            import os
            import glob
            import pandas as pd

            # 扫描daily目录下的CSV文件
            daily_dir = os.path.join(os.getcwd(), "data", "stocks", "daily")
            if not os.path.exists(daily_dir):
                return []

            # 获取所有CSV文件
            csv_files = glob.glob(os.path.join(daily_dir, "*_1d.csv"))

            # 提取股票代码和名称
            symbols_with_names = []
            for file_path in csv_files:
                filename = os.path.basename(file_path)
                symbol = filename.replace("_1d.csv", "")

                # 保留所有6位数字代码（包括股票、债券等）
                if len(symbol) == 6 and symbol.isdigit():
                    # 尝试获取股票名称
                    name = self.get_stock_name(symbol)
                    symbols_with_names.append((symbol, name))

            # 按股票代码排序
            symbols_with_names.sort(key=lambda x: x[0])
            return symbols_with_names  # 显示所有可用股票

        except Exception as e:
            print(f"获取股票代码和名称列表失败: {e}")
            return []

    def get_stock_name(self, symbol: str) -> str:
        """获取股票/债券名称"""
        try:
            # 根据代码前缀判断类型并返回相应名称
            if symbol.startswith('00') or symbol.startswith('30'):
                return self.get_szse_name(symbol)  # 深交所
            elif symbol.startswith('60') or symbol.startswith('68'):
                return self.get_sse_name(symbol)   # 上交所
            elif symbol.startswith('12') or symbol.startswith('11'):
                return f"可转债{symbol}"  # 可转债
            elif symbol.startswith('13'):
                return f"国债{symbol}"    # 国债
            else:
                return f"证券{symbol}"    # 其他证券

        except Exception as e:
            return f"证券{symbol}"

    def get_szse_name(self, symbol: str) -> str:
        """获取深交所股票名称"""
        szse_names = {
            "000001": "平安银行", "000002": "万科A", "000003": "PT金田A",
            "000005": "世纪星源", "000006": "深振业A", "000007": "全新好",
            "000008": "神州高铁", "000009": "中国宝安", "000010": "美丽生态",
            "000011": "深物业A", "000012": "南玻A", "000013": "国星光电",
            "000014": "沙河股份", "000015": "华夏银行", "000016": "深康佳A",
            "000017": "深中华A", "000018": "神州长城", "000019": "深深宝A",
            "000020": "深华发A", "000021": "深科技", "000022": "深赤湾A",
            "000023": "深天地A", "000024": "招商地产", "000025": "特力A",
            "000026": "飞亚达A", "000027": "深圳能源", "000028": "国药一致",
            "000029": "深深房A", "000030": "富奥股份", "000031": "中粮地产",
            "000032": "深桑达A", "000033": "新都退", "000034": "神州数码",
            "000035": "中国天楹", "000036": "华联控股", "000037": "深南电A",
            "000038": "深大通", "000039": "中集集团", "000040": "东旭蓝天",
        }
        return szse_names.get(symbol, f"深股{symbol}")

    def get_sse_name(self, symbol: str) -> str:
        """获取上交所股票名称"""
        sse_names = {
            "600000": "浦发银行", "600001": "邯郸钢铁", "600002": "齐鲁石化",
            "600003": "东北高速", "600004": "白云机场", "600005": "武钢股份",
            "600006": "东风汽车", "600007": "中国国贸", "600008": "首创股份",
            "600009": "上海机场", "600010": "包钢股份", "600011": "华能国际",
            "600012": "皖通高速", "600013": "华夏银行", "600014": "一汽轿车",
            "600015": "华夏银行", "600016": "民生银行", "600017": "日照港",
            "600018": "上港集团", "600019": "宝钢股份", "600020": "中原高速",
            "600021": "上海电力", "600022": "山东钢铁", "600023": "浙能电力",
            "600024": "复旦复华", "600025": "华能水电", "600026": "中远海能",
            "600027": "华电国际", "600028": "中国石化", "600029": "南方航空",
            "600030": "中信证券", "600031": "三一重工", "600032": "浙江新能",
        }
        return sse_names.get(symbol, f"沪股{symbol}")

    def on_search_changed(self, event=None):
        """搜索框内容变化时的处理"""
        try:
            search_text = self.kline_search_var.get().strip().upper()

            if not search_text:
                # 如果搜索框为空，显示所有股票
                filtered_symbols = self.available_symbols
            else:
                # 根据搜索文本过滤股票
                filtered_symbols = []
                for code, name in self.available_symbols:
                    if (search_text in code.upper() or
                        search_text in name.upper() or
                        search_text in code or
                        search_text in name):
                        filtered_symbols.append((code, name))

            # 更新下拉框的值
            combo_values = [f"{code} - {name}" for code, name in filtered_symbols]
            self.kline_symbol_combo['values'] = combo_values

            # 如果只有一个匹配项，自动选择
            if len(filtered_symbols) == 1:
                self.kline_symbol_var.set(combo_values[0])
            elif len(filtered_symbols) > 0 and search_text:
                # 如果有多个匹配项，选择第一个
                self.kline_symbol_var.set(combo_values[0])

        except Exception as e:
            print(f"搜索处理失败: {e}")

    def update_stock_info_panel(self, symbol: str, data):
        """更新股票信息面板"""
        try:
            if data is None or data.empty:
                return

            # 获取股票名称
            stock_name = self.get_stock_name(symbol)

            # 计算统计信息
            latest_data = data.iloc[-1]
            first_data = data.iloc[0]

            current_price = latest_data['close']
            open_price = latest_data['open']
            high_price = data['high'].max()
            low_price = data['low'].min()
            volume = latest_data.get('volume', 0)

            # 计算涨跌
            price_change = current_price - first_data['close']
            price_change_pct = (price_change / first_data['close']) * 100

            # 计算技术指标
            ma5 = data['close'].rolling(window=5).mean().iloc[-1] if len(data) >= 5 else current_price
            ma10 = data['close'].rolling(window=10).mean().iloc[-1] if len(data) >= 10 else current_price
            ma20 = data['close'].rolling(window=20).mean().iloc[-1] if len(data) >= 20 else current_price

            # 计算RSI
            rsi_data = self.calculate_rsi(data['close'])
            current_rsi = rsi_data.iloc[-1] if rsi_data is not None and not rsi_data.empty else 0

            # 计算MACD
            macd_data = self.calculate_macd(data['close'])
            current_macd = macd_data['MACD'].iloc[-1] if macd_data and not macd_data['MACD'].empty else 0
            current_signal = macd_data['Signal'].iloc[-1] if macd_data and not macd_data['Signal'].empty else 0

            # 格式化信息文本
            info_text = f"""
╔══════════════════════════════╗
║          股票信息            ║
╚══════════════════════════════╝

📊 基本信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
股票代码: {symbol}
股票名称: {stock_name}
数据日期: {latest_data['date'].strftime('%Y-%m-%d')}
数据天数: {len(data)} 天

💰 价格信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
当前价格: ¥{current_price:.2f}
开盘价格: ¥{open_price:.2f}
最高价格: ¥{high_price:.2f}
最低价格: ¥{low_price:.2f}

📈 涨跌信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
价格变动: {"+" if price_change >= 0 else ""}{price_change:.2f}
涨跌幅度: {"+" if price_change_pct >= 0 else ""}{price_change_pct:.2f}%
成交量: {volume:,.0f}

📊 技术指标
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
MA5:  ¥{ma5:.2f}
MA10: ¥{ma10:.2f}
MA20: ¥{ma20:.2f}
RSI:  {current_rsi:.2f}
MACD: {current_macd:.4f}
信号线: {current_signal:.4f}

📋 市场状态
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
趋势: {"上涨" if price_change > 0 else "下跌" if price_change < 0 else "平盘"}
RSI状态: {"超买" if current_rsi > 70 else "超卖" if current_rsi < 30 else "正常"}
MACD信号: {"金叉" if current_macd > current_signal else "死叉"}

🎯 支撑阻力
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
阻力位: ¥{high_price:.2f}
支撑位: ¥{low_price:.2f}
中轴线: ¥{(high_price + low_price) / 2:.2f}
"""

            # 更新文本框
            self.stock_info_text.config(state=tk.NORMAL)
            self.stock_info_text.delete(1.0, tk.END)
            self.stock_info_text.insert(1.0, info_text)
            self.stock_info_text.config(state=tk.DISABLED)

        except Exception as e:
            print(f"更新股票信息面板失败: {e}")
            # 显示错误信息
            error_text = f"""
╔══════════════════════════════╗
║          错误信息            ║
╚══════════════════════════════╝

❌ 无法加载股票信息

股票代码: {symbol}
错误原因: {str(e)}

请检查数据文件是否存在。
"""
            self.stock_info_text.config(state=tk.NORMAL)
            self.stock_info_text.delete(1.0, tk.END)
            self.stock_info_text.insert(1.0, error_text)
            self.stock_info_text.config(state=tk.DISABLED)

    def plot_plotly_kline_chart(self, data, symbol: str, period: str):
        """绘制现代化Plotly K线图（集成到软件中）"""
        try:
            from src.plotly_kline import PlotlyKlineChart
            import webbrowser
            import tkinter.messagebox as messagebox
            from PIL import Image, ImageTk

            # 清除之前的图表
            for widget in self.kline_chart_frame.winfo_children():
                widget.destroy()

            # 创建Plotly K线图
            plotly_chart = PlotlyKlineChart()
            html_file = plotly_chart.create_kline_chart(data, symbol, period)

            if html_file:
                # 生成PNG图片用于在tkinter中显示
                image_file = plotly_chart.save_to_image(width=1000, height=700)

                if image_file and os.path.exists(image_file):
                    # 创建按钮框架
                    button_frame = tk.Frame(self.kline_chart_frame)
                    button_frame.pack(fill=tk.X, padx=10, pady=5)

                    # 在浏览器中打开按钮（高级功能）
                    open_btn = tk.Button(
                        button_frame,
                        text="🌐 浏览器中打开（完整交互）",
                        command=lambda: webbrowser.open(f'file://{html_file}'),
                        bg='#4CAF50',
                        fg='white',
                        font=('Arial', 10, 'bold'),
                        relief='flat',
                        padx=15,
                        pady=8
                    )
                    open_btn.pack(side=tk.LEFT, padx=5)

                    # 保存图片按钮
                    save_btn = tk.Button(
                        button_frame,
                        text="💾 保存图表",
                        command=lambda: self.save_plotly_chart(plotly_chart),
                        bg='#2196F3',
                        fg='white',
                        font=('Arial', 10, 'bold'),
                        relief='flat',
                        padx=15,
                        pady=8
                    )
                    save_btn.pack(side=tk.LEFT, padx=5)

                    # 刷新按钮
                    refresh_btn = tk.Button(
                        button_frame,
                        text="🔄 刷新图表",
                        command=lambda: self.refresh_kline_chart(),
                        bg='#FF9800',
                        fg='white',
                        font=('Arial', 10, 'bold'),
                        relief='flat',
                        padx=15,
                        pady=8
                    )
                    refresh_btn.pack(side=tk.LEFT, padx=5)

                    # 创建滚动框架用于显示图片
                    canvas_frame = tk.Frame(self.kline_chart_frame)
                    canvas_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

                    # 创建Canvas和滚动条
                    canvas = tk.Canvas(canvas_frame, bg='white')
                    v_scrollbar = tk.Scrollbar(canvas_frame, orient="vertical", command=canvas.yview)
                    h_scrollbar = tk.Scrollbar(canvas_frame, orient="horizontal", command=canvas.xview)

                    canvas.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)

                    # 布局滚动条和Canvas
                    v_scrollbar.pack(side="right", fill="y")
                    h_scrollbar.pack(side="bottom", fill="x")
                    canvas.pack(side="left", fill="both", expand=True)

                    # 加载并显示图片
                    try:
                        pil_image = Image.open(image_file)
                        # 适当缩放图片以适应窗口
                        display_width = min(950, pil_image.width)
                        display_height = int(pil_image.height * display_width / pil_image.width)
                        pil_image = pil_image.resize((display_width, display_height), Image.Resampling.LANCZOS)

                        photo = ImageTk.PhotoImage(pil_image)

                        # 在Canvas中显示图片
                        canvas.create_image(0, 0, anchor="nw", image=photo)
                        canvas.image = photo  # 保持引用

                        # 设置滚动区域
                        canvas.configure(scrollregion=canvas.bbox("all"))

                        # 添加鼠标滚轮支持
                        def on_mousewheel(event):
                            canvas.yview_scroll(int(-1*(event.delta/120)), "units")
                        canvas.bind("<MouseWheel>", on_mousewheel)

                        # 显示成功信息
                        info_label = tk.Label(
                            button_frame,
                            text=f"📊 {symbol} - {self.get_stock_name(symbol)} ({len(data)}条数据)",
                            fg='#333333',
                            font=('Arial', 10)
                        )
                        info_label.pack(side=tk.RIGHT, padx=10)

                    except Exception as img_error:
                        print(f"显示图片失败: {img_error}")
                        self.show_plotly_fallback_info(data, symbol, period, html_file)

                else:
                    # 如果图片生成失败，显示说明信息
                    self.show_plotly_fallback_info(data, symbol, period, html_file)

                # 显示说明
                info_frame = tk.Frame(self.kline_chart_frame, bg='#f0f0f0')
                info_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

                info_text = tk.Text(
                    info_frame,
                    height=15,
                    bg='#f8f9fa',
                    fg='#333333',
                    font=('Arial', 11),
                    relief='flat',
                    padx=20,
                    pady=20
                )
                info_text.pack(fill=tk.BOTH, expand=True)

                # 插入说明文本
                info_content = f"""
📊 现代化Plotly K线图已生成！

🎯 股票信息：
   • 代码：{symbol}
   • 名称：{self.get_stock_name(symbol)}
   • 周期：{period}
   • 数据量：{len(data)} 条记录

✨ 图表特性：
   • 🕯️ 专业蜡烛图（红涨绿跌）
   • 📈 移动平均线（MA5/MA10/MA20）
   • 📊 成交量分析
   • 📉 MACD技术指标
   • 📈 RSI超买超卖指标

🚀 交互功能：
   • 🔍 缩放：鼠标滚轮或拖拽选择区域
   • 👆 平移：拖拽图表移动视角
   • 📍 悬停：鼠标悬停显示详细数据
   • 🎯 十字线：精确定位数据点
   • 💾 导出：支持PNG/SVG/PDF格式
   • 🔄 重置：双击恢复原始视图

💡 使用提示：
   1. 点击"在浏览器中打开"查看完整交互图表
   2. 在浏览器中可以享受完整的现代化体验
   3. 支持全屏显示和高清导出
   4. 所有技术指标都支持独立缩放

🎨 现代化设计：
   • 深色主题，专业金融风格
   • 响应式布局，适配各种屏幕
   • 流畅动画，提升用户体验
   • 专业配色，符合金融行业标准
                """

                info_text.insert(tk.END, info_content)
                info_text.config(state=tk.DISABLED)

                # 自动在浏览器中打开
                webbrowser.open(f'file://{html_file}')

            else:
                # 显示错误信息
                error_label = tk.Label(
                    self.kline_chart_frame,
                    text="❌ 生成现代化K线图失败，请检查数据格式",
                    fg="red",
                    font=('Arial', 14)
                )
                error_label.pack(expand=True)

        except Exception as e:
            print(f"绘制现代化K线图失败: {e}")
            # 显示错误信息
            error_label = tk.Label(
                self.kline_chart_frame,
                text=f"❌ 绘制现代化K线图失败: {str(e)}",
                fg="red",
                font=('Arial', 12)
            )
            error_label.pack(expand=True)

    def show_plotly_fallback_info(self, data, symbol: str, period: str, html_file: str):
        """显示Plotly图表的回退信息"""
        import webbrowser

        # 创建按钮框架
        button_frame = tk.Frame(self.kline_chart_frame)
        button_frame.pack(fill=tk.X, padx=10, pady=5)

        # 在浏览器中打开按钮
        open_btn = tk.Button(
            button_frame,
            text="🌐 在浏览器中打开现代化K线图",
            command=lambda: webbrowser.open(f'file://{html_file}'),
            bg='#4CAF50',
            fg='white',
            font=('Arial', 12, 'bold'),
            relief='flat',
            padx=20,
            pady=10
        )
        open_btn.pack(side=tk.LEFT, padx=5)

        # 显示说明
        info_frame = tk.Frame(self.kline_chart_frame, bg='#f0f0f0')
        info_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        info_text = tk.Text(
            info_frame,
            height=15,
            bg='#f8f9fa',
            fg='#333333',
            font=('Arial', 11),
            relief='flat',
            padx=20,
            pady=20
        )
        info_text.pack(fill=tk.BOTH, expand=True)

        # 插入说明文本
        info_content = f"""
📊 现代化Plotly K线图已生成！

🎯 股票信息：
   • 代码：{symbol}
   • 名称：{self.get_stock_name(symbol)}
   • 周期：{period}
   • 数据量：{len(data)} 条记录

✨ 图表特性：
   • 🕯️ 专业蜡烛图（红涨绿跌）
   • 📈 移动平均线（MA5/MA10/MA20）
   • 📊 成交量分析
   • 📉 MACD技术指标
   • 📈 RSI超买超卖指标

🚀 交互功能：
   • 🔍 缩放：鼠标滚轮或拖拽选择区域
   • 👆 平移：拖拽图表移动视角
   • 📍 悬停：鼠标悬停显示详细数据
   • 🎯 十字线：精确定位数据点
   • 💾 导出：支持PNG/SVG/PDF格式
   • 🔄 重置：双击恢复原始视图

💡 使用提示：
   1. 点击"在浏览器中打开"查看完整交互图表
   2. 在浏览器中可以享受完整的现代化体验
   3. 支持全屏显示和高清导出
   4. 所有技术指标都支持独立缩放

🎨 现代化设计：
   • 深色主题，专业金融风格
   • 响应式布局，适配各种屏幕
   • 流畅动画，提升用户体验
   • 专业配色，符合金融行业标准
        """

        info_text.insert(tk.END, info_content)
        info_text.config(state=tk.DISABLED)

        # 自动在浏览器中打开
        webbrowser.open(f'file://{html_file}')

    def save_plotly_chart(self, plotly_chart):
        """保存Plotly图表"""
        try:
            from tkinter import filedialog
            filename = filedialog.asksaveasfilename(
                defaultextension=".png",
                filetypes=[("PNG files", "*.png"), ("HTML files", "*.html"), ("All files", "*.*")]
            )
            if filename:
                if filename.endswith('.html'):
                    # 保存HTML文件
                    plotly_chart.fig.write_html(filename)
                else:
                    # 保存图片文件
                    plotly_chart.fig.write_image(filename, width=1200, height=800, scale=2)
                messagebox.showinfo("成功", f"图表已保存到: {filename}")
        except Exception as e:
            messagebox.showerror("错误", f"保存失败: {e}")

    def refresh_kline_chart(self):
        """刷新K线图"""
        try:
            symbol = self.kline_symbol_var.get()
            period = self.kline_period_var.get()
            if symbol:
                self.show_kline_chart(symbol, period)
        except Exception as e:
            print(f"刷新K线图失败: {e}")


def main():
    """主函数"""
    root = tk.Tk()
    app = MainWindow(root)
    root.protocol("WM_DELETE_WINDOW", app.on_closing)
    root.mainloop()


if __name__ == "__main__":
    main()